﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;

using HelixToolkit;
using HelixToolkit.Wpf;

using EQEmu.Files.WLD;
using EQEmu.Files.WLD.Fragments;

namespace EQEmuDisplay3D
{
    public class WldModelDisplay : IDisplay3D, IDisposable
    {
        private List<WLD> _wldCollection = new List<WLD>();
        private ViewClipping _clipping;
        private readonly Model3D _group = new Model3DGroup();

        public WldModelDisplay(IEnumerable<WLD> wlds)
        {
            if (wlds != null)
            {
                _wldCollection.AddRange(wlds);
            }

            Clipping = new ViewClipping();
        }

        public void AddWld(WLD wld)
        {
            _wldCollection.Add(wld);
        }

        public bool RenderModel(string name, int textureNumber = 0, int headNumber = 0, int face=0)
        {
            ModelReference model = null;
            WLD modelWld = null;
            foreach (var wld in _wldCollection)
            {
                model = wld.Models.FirstOrDefault(x => x.FragmentName.Substring(0, 3).ToLower() == name.ToLower());
                modelWld = wld;
                if (model != null) break;
            }
            if (model != null)
            {
                RenderModel(modelWld,model, textureNumber, headNumber, face);
                return true;
            }
            else return false;
        }

        public void RenderModel(WLD modelWld,ModelReference model,int textureNumber=0,int headNumber=0,int face=0)
        {
            var wld = modelWld;
            TrackAnimationBuilder animation = null;
            List<MeshReference> meshrefs = new List<MeshReference>();
            foreach (var refs in model.References)
            {
                var meshref = wld.MeshReferences.Where(x => x.FragmentNumber == refs).FirstOrDefault();
                if (meshref != null)
                {
                    meshrefs.Add(meshref);
                    continue;
                }

                var skel = wld.SkeletonTrackReferences.Where(x => x.FragmentNumber == refs).FirstOrDefault();
                if (skel != null)
                {
                    var skelset = wld.SkeletonTrackSet.Where(x => x.FragmentNumber == skel.SkeletonTrackSetReference).FirstOrDefault();
                    if (skelset != null)
                    {
                        animation = new TrackAnimationBuilder(skelset, wld);

                        foreach (var ms in skelset.MeshReferences)
                        {
                            var m = wld.MeshReferences.Where(x => x.FragmentNumber == ms).FirstOrDefault();
                            if (m != null) meshrefs.Add(m);
                        }
                    }
                }
            }

            List<Mesh> meshes = new List<Mesh>();
            foreach (var m in meshrefs)
            {
                var mesh = wld.ZoneMeshes.FirstOrDefault(x => x.FragmentNumber == m.FragmentReference);
                if (headNumber > 0)
                {
                    //see if it's a head mesh
                    int pos = mesh.FragmentName.IndexOf("HE00");
                    if (pos > 0)
                    {
                        string hNum = String.Format("HE{0:d2}", headNumber);
                        string hMeshName = mesh.FragmentName.Substring(0, pos) + hNum + mesh.FragmentName.Substring(pos + hNum.Length);
                        var hMesh = wld.ZoneMeshes.FirstOrDefault(x => x.FragmentName == hMeshName);
                        if (hMesh != null) mesh = hMesh;
                    }
                }
                if (mesh != null) meshes.Add(mesh);
            }

            if (meshes.Count > 0)
            {
                RenderMesh(wld,meshes, animation, textureNumber,face);
            }            
        }

        public void RenderMesh(WLD wld,IEnumerable<Mesh> meshes, TrackAnimationBuilder animation = null, int textureNum = 0, int face = 0)
        {
            if (meshes == null) return;
            Model3DGroup group = Model as Model3DGroup;

            group.Children.Clear();
            Dictionary<BitmapImage, List<EQEmu.Files.WLD.Polygon>> polysbyTex = new Dictionary<BitmapImage, List<EQEmu.Files.WLD.Polygon>>();
            List<EQEmu.Files.WLD.Polygon> untexturedPolys = new List<EQEmu.Files.WLD.Polygon>();
            foreach (var mesh in meshes)
            {
                foreach (var p in mesh.Polygons)
                {
                    if (p.BitmapInfo != null)
                    {
                        if (polysbyTex.ContainsKey(p.BitmapInfo.Image))
                        {
                            polysbyTex[p.BitmapInfo.Image].Add(p);
                        }
                        else
                        {
                            polysbyTex[p.BitmapInfo.Image] = new List<EQEmu.Files.WLD.Polygon>();
                            polysbyTex[p.BitmapInfo.Image].Add(p);
                        }
                    }
                    else
                    {
                        untexturedPolys.Add(p);
                    }
                }
            }

            Material mat = null;
            foreach (var polytex in polysbyTex)
            {
                MeshBuilder builder = new MeshBuilder();
                if (mat == null)
                {
                    if (polytex.Value.ElementAt(0).BitmapInfo != null)
                    {
                        //mat = HelixToolkit.Wpf.MaterialHelper.CreateImageMaterial(polytex.Value.ElementAt(0).Image, 100.0);
                        BitmapImage img = polytex.Value.ElementAt(0).BitmapInfo.Image;            
                        if (textureNum > 0 || face > 0)
                        {
                            string baseTexture = polytex.Value.ElementAt(0).BitmapInfo.Name;
                            var textureStr = String.Format("{0:d2}", textureNum);
                            var faceStr = String.Format("{0:d1}", face);

                            var index = baseTexture.IndexOf("00");
                            if (index < 0) index = baseTexture.IndexOf("01");

                            if (index > 0)
                            {
                                var faceAndTexture = baseTexture.Substring(0, index) + textureStr + faceStr + baseTexture.Substring(index + textureStr.Length + faceStr.Length);
                                var textureOnly = baseTexture.Substring(0, index) + textureStr + baseTexture.Substring(index + textureStr.Length);
                                var faceOnly = baseTexture.Substring(0, index + textureStr.Length) + faceStr + baseTexture.Substring(index + textureStr.Length + faceStr.Length);

                                if (wld.ImageMapping.ContainsKey(faceAndTexture))
                                {
                                    img = wld.ImageMapping[faceAndTexture].Image;
                                }
                                else if (wld.ImageMapping.ContainsKey(textureOnly))
                                {
                                    img = wld.ImageMapping[textureOnly].Image;
                                }
                                else if (wld.ImageMapping.ContainsKey(faceOnly))
                                {
                                    img = wld.ImageMapping[faceOnly].Image;
                                }
                            }
                        }
                        
                        var brush = new System.Windows.Media.ImageBrush(img);                        
                        brush.ViewportUnits = System.Windows.Media.BrushMappingMode.Absolute;
                        //brush.TileMode
                        brush.TileMode = System.Windows.Media.TileMode.Tile;
                        //brush.Stretch = System.Windows.Media.Stretch.Fill;
                        mat = HelixToolkit.Wpf.MaterialHelper.CreateMaterial(brush);                        
                    }
                    else
                    {
                        mat = Materials.LightGray;
                    }
                }
                foreach (var poly in polytex.Value)
                {
                    Point3D p1 = new Point3D(poly.V1.X, poly.V1.Y, poly.V1.Z);
                    Point3D p2 = new Point3D(poly.V2.X, poly.V2.Y, poly.V2.Z);
                    Point3D p3 = new Point3D(poly.V3.X, poly.V3.Y, poly.V3.Z);

                    if (animation != null)
                    {
                        if (animation.SkeletonPieceTransforms.ContainsKey(poly.V1.BodyPiece))
                        {
                            var atrans = animation.SkeletonPieceTransforms[poly.V1.BodyPiece];
                            p1 = atrans.Transform(p1);
                        }

                        if (animation.SkeletonPieceTransforms.ContainsKey(poly.V2.BodyPiece))
                        {
                            var atrans = animation.SkeletonPieceTransforms[poly.V2.BodyPiece];
                            p2 = atrans.Transform(p2);
                        }

                        if (animation.SkeletonPieceTransforms.ContainsKey(poly.V3.BodyPiece))
                        {
                            var atrans = animation.SkeletonPieceTransforms[poly.V3.BodyPiece];
                            p3 = atrans.Transform(p3);
                        }
                    }

                    var rotate = new RotateTransform3D();
                    rotate.Rotation = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 90);
                    p1 = rotate.Transform(p1);
                    p2 = rotate.Transform(p2);
                    p3 = rotate.Transform(p3);

                    if (!Clipping.DrawPoint(p1) || !Clipping.DrawPoint(p2) || !Clipping.DrawPoint(p3))
                    {
                        continue;
                    }

                    //v coordinate - negate it to convert from opengl coordinates to directx
                    var t1 = new System.Windows.Point(poly.V1.U, 1 - poly.V1.V);
                    var t2 = new System.Windows.Point(poly.V2.U, 1 - poly.V2.V);
                    var t3 = new System.Windows.Point(poly.V3.U, 1 - poly.V3.V);

                    //var t1 = new System.Windows.Point(0.0, 0.0);
                    //var t2 = new System.Windows.Point(2.0, 0.0);
                    //var t3 = new System.Windows.Point(0.0, 2.0);
                    //builder.AddTriangle(p3, p2, p1, t3, t2, t1);
                    builder.AddTriangle(p3, p2, p1, t3, t2, t1);
                }
                group.Children.Add(new GeometryModel3D(builder.ToMesh(), mat));
                mat = null;
            }

            //create the untextured polygons... basically a copy and paste from above which sucks... TODO
            var bbuilder = new MeshBuilder();
            mat = Materials.LightGray;
            foreach (var poly in untexturedPolys)
            {
                Point3D p1 = new Point3D(poly.V1.X, poly.V1.Y, poly.V1.Z);
                Point3D p2 = new Point3D(poly.V2.X, poly.V2.Y, poly.V2.Z);
                Point3D p3 = new Point3D(poly.V3.X, poly.V3.Y, poly.V3.Z);
                var rotate = new RotateTransform3D();
                rotate.Rotation = new AxisAngleRotation3D(new Vector3D(0, 0, 1), 90);
                p1 = rotate.Transform(p1);
                p2 = rotate.Transform(p2);
                p3 = rotate.Transform(p3);

                if (!Clipping.DrawPoint(p1) || !Clipping.DrawPoint(p2) || !Clipping.DrawPoint(p3))
                {
                    continue;
                }
                bbuilder.AddTriangle(p3, p2, p1);
            }
            group.Children.Add(new GeometryModel3D(bbuilder.ToMesh(), mat));
        }

        void clipping_OnClippingChanged(object sender, EventArgs e)
        {
        }

        public System.Windows.Media.Media3D.Model3D Model
        {
            get { return _group; }
        }

        public ViewClipping Clipping
        {
            get { return _clipping; }
            set
            {
                if (_clipping != null)
                {
                    _clipping.OnClippingChanged -= clipping_OnClippingChanged;
                }
                _clipping = value;
                _clipping.OnClippingChanged += new ViewClipping.ClippingChangedHandler(clipping_OnClippingChanged);
            }
        }

        public void Dispose()
        {
            if (_clipping != null)
            {
                _clipping.OnClippingChanged -= clipping_OnClippingChanged;
            }
        }
    }
}
